package com.director.spring;

import com.director.core.DirectAction;
import com.director.core.DirectException;
import com.director.core.DirectMethod;
import com.director.core.DirectTransactionData;
import com.director.core.DirectTransactionResult;
import com.director.core.ExecutorAdapter;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.StaticMethodMatcherPointcut;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author Simone Ricciardi
 * @version 1.0, 12/03/2011
 */
class AdvisedActionExecutorAdapter implements ExecutorAdapter {

   private Advised action;

   AdvisedActionExecutorAdapter(Advised action) {
      this.action = action;
   }

   @Override
   public DirectTransactionResult execute(DirectAction directAction, DirectMethod directMethod, DirectTransactionData data)
         throws DirectException {

      try {
         Object targetAction = this.action.getTargetSource().getTarget();
         ProxyFactory factory = new ProxyFactory(targetAction);
         factory.setProxyTargetClass(true);

         Advisor[] advisors = this.action.getAdvisors();
         for(Advisor advisor : advisors) {
            factory.addAdvisor(advisor);
         }

         DirectMethodInterceptor methodInterceptor = new DirectMethodInterceptor(directAction, directMethod, data);
         factory.addAdvisor(new DefaultPointcutAdvisor(new DirectMethodPointcut(directMethod), methodInterceptor));

         Object proxy = factory.getProxy();
         Object[] params = directMethod.getDefaultParameterValues();
         directMethod.getMethod().invoke(proxy, params);

         return methodInterceptor.result;
      } catch(InvocationTargetException e) {
         String message = "Cannot invoke the action method " + directMethod + " of the direct class " + directAction;
         throw new DirectException(message, e.getTargetException());
      } catch(Throwable e) {
         String message = "Cannot invoke the action method " + directMethod + " of the direct class " + directAction;
         throw new DirectException(message, e);
      }
   }

   /*@Aspect
   class DirectMethodInterceptor implements Ordered {

      DirectMethod directMethod;
      DirectTransactionData data;
      DirectTransactionResult result;

      public DirectMethodInterceptor(DirectMethod directMethod, DirectTransactionData data) {
         this.directMethod = directMethod;
         this.data = data;
      }

      @Pointcut("execution(public * *(..))")
      public void directMethod() {
      }

      @Around("directMethod()")
      public Object execute(ProceedingJoinPoint pjp) throws Throwable {
         Object[] params = directMethod.parseParameters(data);
         Object actionResult = (params != null) ? pjp.proceed(params) : pjp.proceed();
         JsonParser parser = DirectContext.get().getConfiguration().getParser();
         this.result = parser.buildResult(directMethod, actionResult);
         return actionResult;
      }

      @Override
      public int getOrder() {
         return LOWEST_PRECEDENCE;
      }
   }*/

   class DirectMethodPointcut extends StaticMethodMatcherPointcut {

      DirectMethod directMethod;

      DirectMethodPointcut(DirectMethod directMethod) {
         this.directMethod = directMethod;
      }

      @Override
      public boolean matches(Method method, Class<?> targetClass) {
         return directMethod.getMethod().equals(method);
      }
   }

   class DirectMethodInterceptor implements MethodInterceptor {

      DirectAction directAction;
      DirectMethod directMethod;
      DirectTransactionData data;
      DirectTransactionResult result;

      public DirectMethodInterceptor(DirectAction directAction, DirectMethod directMethod, DirectTransactionData data) {
         this.directAction = directAction;
         this.directMethod = directMethod;
         this.data = data;
      }

      @Override
      public Object invoke(MethodInvocation invocation) throws Throwable {

         DefaultActionExecutorAdapter executorAdapter = new DefaultActionExecutorAdapter(invocation.getThis());
         this.result = executorAdapter.execute(this.directAction, this.directMethod, this.data);
         return null;
      }
   }
}
